<template>
    <div class="h-100 w-100 d-flex" style="overflow-x: hidden;" v-if="pomPath">
        <ModelingAside ref="aside"
            :pom-path="pomPath"
            :selectable="coding"
            @to-edit="toEditClass"
            @class-deleted="closeEditClass"
            @package-change="onPackageChange"
            @class-change="onClassChange"
            @refresh="onRefresh"
            @selected="onSelectedClass"
        />
        <GenerateCodeBatch class="w-100 p-3" :app="$refs.aside.currentApp" :class-ids="codingClassIds" v-if="coding"/>
        <div class="flex-1 d-flex flex-column bg-muted" v-else>
            <el-tabs v-model="activeClassId" class="h-100 border-0" type="border-card" closable
                @tab-remove="closeEditClass"
                v-if="openingClasses.length">
                <template v-for="(clazz, index) of openingClasses" :key="index">
                    <el-tab-pane class="h-100"
                        :label="clazz.simpleName"
                        :name="clazz.id"
                        v-if="findApp(clazz.appName)">
                        <template #label>
                            <div class="flex-v-center" :title="clazz.getName()">
                                <NodeIcon class="me-1" :type="clazz.kind.toString()"/>
                                <span class="text-secondary me-1" v-if="!singleArch">
                                    {{ findApp(clazz.appName)?.name }}:</span>
                                <span class="ms-0" :class="{'abstract-class-name': clazz.abstractClass}">
                                    {{ clazz.simpleName }}</span>
                                <span class="ms-1 text-placeholder"
                                    v-if="clazz.caption">({{ clazz.caption }})</span>
                                <span class="ms-1 text-regular" v-if="clazz.modified">*</span>
                            </div>
                        </template>
                        <el-scrollbar :ref="'scrollbar-' + clazz.id" class="pe-3" always>
                            <div :key="clazz.kind">
                                <ClassEditEntity ref="classEditors"
                                    :app="findApp(clazz.appName)"
                                    :clazz="clazz"
                                    :save="saveClass"
                                    :active="activeClassId === clazz.id"
                                    @initialized="onEditInitialized"
                                    @open-class="openClass"
                                    @open-update="toUpdateClass"
                                    @open-setting="$refs.aside.toAppSetting(clazz.appName,'table')"
                                    v-if="clazz instanceof EntityClass"/>
                                <ClassEditEnum ref="classEditors"
                                    :app="findApp(clazz.appName)"
                                    :clazz="clazz"
                                    :save="saveClass"
                                    :active="activeClassId === clazz.id"
                                    v-else-if="TypeKind.ENUM.equals(clazz.kind)"/>
                                <ClassEditValue ref="classEditors"
                                    :app="findApp(clazz.appName)"
                                    :clazz="clazz"
                                    :save="saveClass"
                                    :active="activeClassId === clazz.id"
                                    @open-class="openClass"
                                    @open-update="toUpdateClass"
                                    v-else-if="TypeKind.VALUE.equals(clazz.kind)"/>
                                <ClassEditPlain ref="classEditors"
                                    :app="findApp(clazz.appName)"
                                    :clazz="clazz"
                                    :save="saveClass"
                                    :active="activeClassId === clazz.id"
                                    @open-class="openClass"
                                    @open-update="toUpdateClass"
                                    v-else-if="TypeKind.PLAIN.equals(clazz.kind)"/>
                            </div>
                        </el-scrollbar>
                    </el-tab-pane>
                </template>
            </el-tabs>
        </div>
    </div>
</template>

<script>
import tnx from '@/tnx';
import TypeKind from '@/logic/modeling/enums/TypeKind.js';
import TableNameRule from '@/logic/modeling/entity/TableNameRule.js';
import StandardClass from '@/logic/modeling/entity/StandardClass.js';
import EntityClass from '@/logic/modeling/entity/EntityClass.js';
import EntityProperty from '@/logic/modeling/entity/EntityProperty.js';
import ValueClass from '@/logic/modeling/entity/ValueClass.js';
import EmbeddedPropertyMapping from '@/logic/modeling/entity/EmbeddedPropertyMapping.js';
import {STRING_CLASS_NAME} from '@/logic/modeling/util/dependency-lib-jdk.js';
import PersistUtil from '@/logic/modeling/util/persist.js';

import ModelingAside from './aside.vue';
import ClassEditEntity from './class-edit-entity.vue';
import ClassEditEnum from './class-edit-enum.vue';
import ClassEditPlain from './class-edit-plain.vue';
import ClassEditValue from './class-edit-value.vue';
import NodeIcon from './components/NodeIcon.vue';
import GenerateCodeBatch from './generate-code-batch.vue';

export default {
    components: {
        ModelingAside,
        ClassEditEntity,
        ClassEditEnum,
        ClassEditValue,
        ClassEditPlain,
        NodeIcon,
        GenerateCodeBatch,
    },
    data() {
        return {
            pomPath: this.$route.query.pomPath,
            coding: false,
            codingClassIds: [],
            openingClasses: [],
            classNameAppMapping: {}, // 类名-app映射集
            activeClassId: null, // 当前激活的模型类名
        };
    },
    computed: {
        TypeKind() {
            return TypeKind;
        },
        EntityClass() {
            return EntityClass;
        },
        singleArch() {
            return tnx.app.global.modelingProject.isSingleArch();
        },
    },
    mounted() {
        if (!this.pomPath) {
            this.$router.replace('/');
        } else {
            tnx.app.eventBus.on('tnxtool.modeling.saveAll', () => {
                this.saveOpeningClasses();
            });
            tnx.app.eventBus.on('tnxtool.modeling.batchGenerateCode', active => {
                this.coding = active;
            });
        }
    },
    unmounted() {
        tnx.app.eventBus.off('tnxtool.modeling.saveAll');
        tnx.app.eventBus.off('tnxtool.modeling.batchGenerateCode');
    },
    methods: {
        findApp(appName) {
            return tnx.app.global.modelingProject.findApp(appName);
        },
        toUpdateClass(clazz) {
            this.$refs.aside.toUpdateClass(Object.assign({}, clazz, {
                app: clazz.appName,
            }));
        },
        toEditClass(app, classId) {
            if (this.coding) {
                return false;
            }
            if (!app.tableNameRule) {
                app.tableNameRule = new TableNameRule();
            }
            if (!this.openingClasses.contains(c => c.matches(classId))) {
                let clazz = app.findClass(classId);
                if (clazz) {
                    clazz = clazz.clone();
                    clazz.appName = app.name;
                    this.openingClasses.push(clazz);
                } else {
                    return false;
                }
            }
            this.activeClassId = classId;
            return true;
        },
        onEditInitialized(clazz) {
            let scrollbar = this.$refs['scrollbar-' + clazz.id][0];
            if (scrollbar) {
                scrollbar.setScrollTop(0);
            }
        },
        openClass(classId) {
            let project = tnx.app.global.modelingProject;
            for (let app of project.apps) {
                if (this.toEditClass(app, classId)) {
                    return;
                }
            }
            tnx.toast('不是当前工程下的建模类，不能打开', 2000, {
                type: 'warning',
            });
        },
        closeEditClass(classId) {
            let index = tnx.util.array.indexOf(this.openingClasses, c => c.matches(classId));
            if (index >= 0) {
                this.openingClasses.splice(index, 1);
                // 如果关闭的是当前激活类，则激活旁边打开的类
                if (this.activeClassId === classId) {
                    let nextClass = this.openingClasses[index] || this.openingClasses[index - 1];
                    if (nextClass) {
                        this.activeClassId = nextClass.id;
                    } else {
                        this.activeClassId = null;
                    }
                }
            }
        },
        onPackageChange(oldPackageName, newPackageName) {
            for (let openingClass of this.openingClasses) {
                if (openingClass.packageName === oldPackageName) {
                    openingClass.packageName = newPackageName;
                }
            }
        },
        onClassChange(oldApp, oldClass, newApp, newClass) {
            for (let i = 0; i < this.openingClasses.length; i++) {
                let openingClass = this.openingClasses[i];
                if (openingClass.id === oldClass.id) { // 当前改动类如果已打开，则更新该打开类
                    openingClass.update(newClass);
                    openingClass.kind = newClass.kind;
                    openingClass.appName = newApp.name;
                    openingClass.refreshTime = new Date();
                } else { // 打开类如果是当前改动类的子类，则重新加载该类
                    let app = this.findApp(openingClass.appName);
                    if (app.isExtended(oldClass.id, openingClass.id)) {
                        this.reloadOpeningClass(app, openingClass);
                    }
                }
            }
        },
        reloadOpeningClass(app, openingClass) {
            openingClass.update(app.findClass(openingClass.id));
            openingClass.appName = app.name;
            openingClass.refreshTime = new Date();
        },
        saveClass(classId, app, model, callback) {
            let clazz = app.updateClass(classId, model);
            if (clazz) {
                if (clazz instanceof StandardClass) {
                    let duplicatedPropertyName = clazz.findDuplicatedPropertyName();
                    if (duplicatedPropertyName) {
                        tnx.error('属性名重复：' + duplicatedPropertyName);
                        callback(false);
                        return;
                    }
                    if (clazz instanceof EntityClass) {
                        let duplicatedPhysicalFieldName = clazz.findDuplicatedPhysicalFieldName();
                        if (duplicatedPhysicalFieldName) {
                            tnx.error('数据库字段名重复：' + duplicatedPhysicalFieldName);
                            callback(false);
                            return;
                        }
                    }
                    // 修正属性上不合理的配置
                    for (let property of clazz.properties) {
                        let refClass = app.findClass(property.typeId, true, true);
                        // 去掉多余的泛型设定
                        if (!refClass || !refClass.declaredGenericTypes?.length) {
                            property.genericTypes = [];
                        }
                        // 如果性质不支持数组，则去掉数组设定
                        if (!refClass.kind.supports('arrayed')) {
                            property.arrayed = false;
                        }
                        // 只有字符串类型的属性才有是否用户填写的选项
                        if (property.typeId !== STRING_CLASS_NAME || property.arrayed) {
                            property.manualInput = false;
                        }
                    }
                }
                clazz.saveTime = new Date();
                app.syncReferences(true);
                let affectedClassIds = new Set();
                PersistUtil.syncSubClasses(tnx.app.global.modelingProject, clazz, modelingApp => {
                    // 当前类如果为值模型，则同步所有引用了该类的属性内嵌字段
                    let result = false;
                    if (clazz instanceof ValueClass) {
                        for (let modelingClass of modelingApp.classes) {
                            if (modelingClass instanceof EntityClass) {
                                for (let entityProperty of modelingClass.properties) {
                                    if (entityProperty.typeId === clazz.id) {
                                        let entityPropertyPhysicalFieldName = entityProperty.mapping?.name
                                            || entityProperty.getDefaultPhysicalFieldName();
                                        let embedded = [];
                                        for (let i = 0; i < clazz.properties.length; i++) {
                                            let valueProperty = EntityProperty.of(clazz.properties[i]);
                                            let valuePropertyClass = app.findClass(valueProperty.typeId, true, true);
                                            if (valuePropertyClass) {
                                                valueProperty.refKind = valuePropertyClass.kind;
                                            }
                                            let defaultPhysicalFieldName = entityPropertyPhysicalFieldName + '_'
                                                + valueProperty.getDefaultPhysicalFieldName();
                                            let embeddedMapping = entityProperty.embedded.find(
                                                field => field.logicPropertyId === valueProperty.id);
                                            if (embeddedMapping) {
                                                if (embeddedMapping.logicPropertyName !== valueProperty.name) {
                                                    embeddedMapping.name = defaultPhysicalFieldName;
                                                }
                                                Object.assign(embeddedMapping, {
                                                    logicPropertyName: valueProperty.name,
                                                    caption: valueProperty.caption || undefined,
                                                });
                                            } else {
                                                embeddedMapping = EmbeddedPropertyMapping.of({
                                                    logicPropertyId: valueProperty.id,
                                                    logicPropertyName: valueProperty.name,
                                                    caption: valueProperty.caption || undefined,
                                                    name: defaultPhysicalFieldName,
                                                    nullable: true,
                                                });
                                            }
                                            embeddedMapping.ordinal = i;
                                            embedded.push(embeddedMapping);
                                        }
                                        embedded.sort((em1, em2) => em1.ordinal - em2.ordinal);
                                        entityProperty.embedded = embedded.filter(em => {
                                            delete em.ordinal;
                                            return em;
                                        });
                                        result = true;
                                        affectedClassIds.add(modelingClass.id);
                                    }
                                }
                            }
                        }
                    }
                    return result;
                });
                PersistUtil.saveApp(app).then(() => {
                    for (let openingClass of this.openingClasses) {
                        if (openingClass.id === classId) {
                            openingClass.update(clazz);
                        } else {
                            let openingApp = this.findApp(openingClass.appName);
                            if (openingApp.isExtended(classId, openingClass.id)
                                || affectedClassIds.has(openingClass.id)) {
                                this.reloadOpeningClass(openingApp, openingClass);
                            }
                        }
                    }
                    callback(true);
                }).catch(error => {
                    // 重新加载应用和打开的模型类
                    this.$refs.aside.loadApp(app.path, newApp => {
                        app.update(newApp);
                        for (let openingClass of this.openingClasses) {
                            this.reloadOpeningClass(app, openingClass);
                        }
                    });
                    tnx.error(error);
                    callback(false);
                });
            }
        },
        saveOpeningClasses() {
            let classEditors = this.$refs.classEditors;
            if (classEditors) {
                if (!Array.isArray(classEditors)) {
                    classEditors = [classEditors];
                }
                let savedNum = 0;
                tnx.showLoading();
                for (let classEditor of classEditors) {
                    classEditor.doSave(() => {
                        if (++savedNum === classEditors.length) {
                            tnx.closeLoading();
                        }
                    });
                }
            }
        },
        onRefresh(project) {
            tnx.app.global.modelingProject = project; // 缓存在全局变量集中
            let closeClasses = [];
            for (let i = 0; i < this.openingClasses.length; i++) {
                let openingClass = this.openingClasses[i];
                let app = project.findApp(openingClass.appName);
                if (app) {
                    let newClass = app.findClass(openingClass);
                    if (newClass) {
                        newClass = newClass.clone();
                        newClass.appName = app.name;
                        this.openingClasses[i] = newClass;
                        continue;
                    }
                }
                closeClasses.push(openingClass);
            }
            for (let clazz of closeClasses) {
                this.closeEditClass(clazz.id);
            }
        },
        onSelectedClass(classIds) {
            this.codingClassIds = classIds;
        },
    },
}
</script>

<style lang="scss" scoped>
:deep(.el-tabs) {
    .el-tabs__header {
        .el-tabs__item {
            .tnxel-icon {
                margin-top: 2px;
            }
        }
    }

    .el-tabs__content {
        padding-right: 0.5rem;
    }
}
</style>
